package com.syl.starter.itools.common.utils;

import com.syl.starter.itools.base.beans.DoubleResult;
import com.syl.starter.itools.base.constants.DateConstant;
import com.syl.starter.itools.base.exception.ReflectException;
import com.syl.starter.itools.common.enums.DateUnitEnum;
import com.syl.starter.itools.common.enums.DateVerifyFormatEnum;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.util.Date;

/**
 * 日期时间工具类
 * @author syl
 * @create 2018-06-14 10:15
 **/
public class DateUtils {
    private static Logger LOG = LoggerFactory.getLogger(DateUtils.class);
    private static final String PLUS = "plus";
    private static final String MINUS = "minus";
    @Getter
    private LocalDateTime localDateTime;
    private static final int oneMonthX = 30;
    private static final int oneYearX = 365;

    /**
     * Date转换为LocalDateTime
     * @param date
     * @return
     */
    public static LocalDateTime convertLocalDateTime(Date date) {
        if(date == null) return null;
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

    /**
     * LocalDateTime转换为Date
     * @param time
     * @return
     */
    public static Date convertDate(LocalDateTime time) {
        if (time == null)
            return null;
        return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 以当前时间构造日期
     */
    public DateUtils(){
        localDateTime = LocalDateTime.now();
    }

    /**
     * 以yyyy-MM-dd HH:mm:ss格式的日期字符串构造日期 不对日期格式做效验
     * @param date
     */
    public DateUtils(String date){
        this(date, DateConstant.DATE_TIME_NORMAL);
    }

    /**
     * 以指定日期格式的日期字符串构造日期 不对日期格式做效验
     * 构造不出来会为空
     * @param date
     * @param format
     */
    public DateUtils(String date, String format){
        this(date, new String[]{format});
    }

    /**
     * 以指定日期格式的日期字符串构造日期 不对日期格式做效验
     * 构造不出来会为空
     * @param date
     * @param formatArr 尽可能的日期格式类型数组
     */
    public DateUtils(String date, String... formatArr) {
        for (String format : formatArr) {
            if (StringUtils.isEmpty(date)) continue;
            try {
                if (date.length() == 10){
                    LocalDate parse = LocalDate.parse(date, DateTimeFormatter.ofPattern(format));
                    this.localDateTime = parse.atStartOfDay();
                }else {
                    this.localDateTime = LocalDateTime.parse(date, DateTimeFormatter.ofPattern(format));
                }
            }catch (Exception e){
                LOG.error("构造日期失败 传入日期 {} 格式 {}", date, format);
            }
        }
    }

    /**
     * 以指定日期格式的日期字符串构造日期 效验日期格式
     *
     * @param date
     * @param dateVerifyFormatEnum
     */
    public DateUtils(String date, DateVerifyFormatEnum dateVerifyFormatEnum){
        if(StringUtils.isEmpty(date)) return;
        if(!isDateString(date, dateVerifyFormatEnum))
            return;
        String format = dateVerifyFormatEnum.getFormat();
        if(DateVerifyFormatEnum.SIMPLE_CN_DATE == dateVerifyFormatEnum){
            LocalDate parse = LocalDate.parse(date, DateTimeFormatter.ofPattern(format));
            this.localDateTime = LocalDateTime.of(parse, LocalTime.of(0, 0, 0));
        }else
            this.localDateTime = LocalDateTime.parse(date, DateTimeFormatter.ofPattern(format));
    }

    /**
     * 以LocalDateTime 构造
     * @param localDateTime
     */
    public DateUtils(LocalDateTime localDateTime){
        this.localDateTime = localDateTime;
    }

    /**
     * 简易以LocalDate 构造开始时间为000
     * @param localDate
     */
    public DateUtils(LocalDate localDate){
        this(localDate, LocalTime.of(0,0,0, 0));
    }

    /**
     * 以LocalDate 构造
     * @param localDate 日期对象
     * @param localTime 时分秒
     */
    public DateUtils(LocalDate localDate, LocalTime localTime){
        if(localDate == null)return;
        this.localDateTime = localDate.atTime(localTime);
    }

    /**
     * 以时间戳构造 默认以毫秒构造
     * @param l
     */
    public DateUtils(Long l){
        this(l, 0);
    }

    /**
     * 以时间戳构造 可选择类型
     * @param l 时间戳
     * @param type 0:毫秒 1:秒
     */
    public DateUtils(Long l, int type){
        if(l == null) return;
        if(type == 1)
            l = l * DateConstant.MS_S_UNIT_CONVERSION;
        this.localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(l), ZoneId.systemDefault());
    }

    /**
     * 以Date 构造
     * @param date
     */
    public DateUtils(Date date){
        if(date == null) return;
        localDateTime = convertLocalDateTime(date);
    }

    /**
     * 获取当前datatime
     * @return
     */
    public Date getDate(){
        return convertDate(localDateTime);
    }

    /**
     * 获取当前时间 默认格式化为yyyy-MM-dd HH:mm:ss
     * @return
     */
    public String format(){
        return format(null);
    }

    /**
     * 获取当前时间格式化
     * @param format format为空时取默认时间格式
     * @return localDateTime为空时为null 否则正常
     */
    public String format(String format){
        if(StringUtils.isEmpty(format))
            format = DateConstant.DATE_TIME_NORMAL;
        if(localDateTime == null)
            return null;
        return localDateTime.format(DateTimeFormatter.ofPattern(format));
    }

    /**
     * 获取年
     * @return localDateTime为空时为-1 否则正常
     */
    public int getYear(){
        if(localDateTime == null)
            return -1;
        return localDateTime.getYear();
    }

    /**
     * 获取月份
     * @return localDateTime为空时为-1 否则正常
     */
    public int getMonth(){
        if(localDateTime == null)
            return -1;
        return localDateTime.getMonthValue();
    }

    /**
     * 获取当前月第几周
     * @return localDateTime为空时为-1 否则正常
     */
    public int getWeekMonth(){
        if(localDateTime == null)
            return -1;
        return localDateTime.get(ChronoField.ALIGNED_WEEK_OF_MONTH);
    }

    /**
     * 获取当前年第几周
     * @return localDateTime为空时为-1 否则正常
     */
    public int getWeekYear(){
        if(localDateTime == null)
            return -1;
        return localDateTime.get(ChronoField.ALIGNED_WEEK_OF_YEAR);
    }

    /**
     * 获取当前月第几天
     * @return localDateTime为空时为-1 否则正常
     */
    public int getDayMonth(){
        if(localDateTime == null)
            return -1;
        return localDateTime.getDayOfMonth();
    }

    /**
     * 获取当前年的第几天
     * @return localDateTime为空时为-1 否则正常
     */
    public int getDayYear(){
        if(localDateTime == null)
            return -1;
        return localDateTime.getDayOfYear();
    }

    /**
     * 获取当前星期的第几天
     * @return localDateTime为空时为-1 否则正常
     */
    public int getDayWeek(){
        if(localDateTime == null)
            return -1;
        return localDateTime.getDayOfWeek().getValue();
    }

    /**
     * 获取当前日期小时
     * @return localDateTime为空时为-1 否则正常
     */
    public int getHour(){
        if(localDateTime == null)
            return -1;
        return localDateTime.getHour();
    }

    /**
     * 获取当前日期分钟
     * @return localDateTime为空时为-1 否则正常
     */
    public int getMinute(){
        if(localDateTime == null)
            return -1;
        return localDateTime.getMinute();
    }

    /**
     * 获取当前日期秒
     * @return localDateTime为空时为-1 否则正常
     */
    public int getSecond(){
        if(localDateTime == null)
            return -1;
        return localDateTime.getSecond();
    }

    /**
     * 获取当前日期毫秒
     * @return localDateTime为空时为-1 否则正常
     */
    public int getMilliSecond(){
        if(localDateTime == null)
            return -1;
        return localDateTime.getNano() / DateConstant.NS_MS_UNIT_CONVERSION;
    }

    /**
     * 获取当前日期纳秒
     * @return localDateTime为空时为-1 否则正常
     */
    public int getNano(){
        if(localDateTime == null)
            return -1;
        return localDateTime.getNano();
    }

    /**
     * 以毫秒获取时间戳
     * @return
     */
    public static long getTimestamp(){
        return System.currentTimeMillis();
    }

    /**
     * 以秒获取时间戳
     * @return
     */
    public static long getSecondTimestamp(){
        return System.currentTimeMillis() / DateConstant.MS_S_UNIT_CONVERSION;
    }

    /**
     * 是否当天
     * @param date
     * @return localDateTime为空时为false 否则正常
     */
    public boolean isToday(Date date) {
        if(localDateTime == null)
            return false;
        return localDateTime.equals(date);
    }

    /**
     * 添加日期
     * @param l
     * @return
     */
    public DateUtils addDate(Integer l) throws ReflectException {
        return this.addDate(l, DateUnitEnum.MILLISECOND);
    }

    /**
     * 添加日期
     * @param l
     * @param unit 单位
     * @return
     */
    public DateUtils addDate(Integer l, DateUnitEnum unit) throws ReflectException {
        return plusOrMinusHelp(PLUS, l, unit);
    }

    /**
     * 减少日期
     * @param l
     * @return
     */
    public DateUtils minusDate(Integer l) throws ReflectException {
        return minusDate(l, DateUnitEnum.MILLISECOND);
    }

    /**
     * 减少日期
     * @param l
     * @param unit 单位
     * @return
     */
    public DateUtils minusDate(Integer l, DateUnitEnum unit) throws ReflectException {
        return plusOrMinusHelp(MINUS, l, unit);
    }

    /**
     *
     * @param type
     * @param l
     * @param unit
     * @return
     * @throws ReflectException
     */
    private DateUtils plusOrMinusHelp(String type, Integer l, DateUnitEnum unit) throws ReflectException {
        if(localDateTime == null || l == null) return this;
        if(DateUnitEnum.MILLISECOND == unit) {
            l = l * DateConstant.NS_MS_UNIT_CONVERSION;  //minus 无毫秒级方法 以纳秒代替
        }
        String methodName = type + unit.getMiniMethodName();
        this.localDateTime = ReflectUtils.methodInvoke(this.localDateTime, methodName, new Object[]{l}, LocalDateTime.class);
        return this;
    }

    /**
     * 以当前时间为基准计算两日期相隔
     * @return 符合的枚举返回 当前时间为空时返回-1
     */
    public long getIntervalDate(Date endDate, DateUnitEnum unitEnum) throws IllegalAccessException {
        if(localDateTime == null) return -1;
        return getIntervalDate(this.localDateTime, convertLocalDateTime(endDate), unitEnum);
    }

    /**
     * 判断字符串是否是正确的日期格式
     * @param date 日期字符串
     * @param format 目前支持的日期格式验证格式
     * @return
     */
    public static boolean isDateString(String date, DateVerifyFormatEnum format){
        if(null == format || DateVerifyFormatEnum.NULL == format)
            return false;
        return RegexUtils.test(format.getRegex(), date);
    }

    /**
     * 获取到明天的秒差, 适合用在零点过期场景
     * @return
     */
    public static Long getTomorrowSecondDiff(){
        try {
            Date tomorrow = new DateUtils().addDate(1, DateUnitEnum.DAY).setDayStart().getDate();
            return DateUtils.getIntervalDate(new Date(), tomorrow, DateUnitEnum.SECOND);
        } catch (ReflectException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 计算两日期间隔时间
     * @param minDate 小时间
     * @param maxDate 大时间
     * @param unitEnum
     *
     * @return 当任意两个时间为null时返回null
     * @throws IllegalAccessException
     */
    public static Long getIntervalDate(Date minDate, Date maxDate, DateUnitEnum unitEnum) throws IllegalAccessException{
        if(minDate == null || maxDate == null) return null;
        return getIntervalDate(convertLocalDateTime(minDate), convertLocalDateTime(maxDate), unitEnum);
    }

    /**
     * 计算两日期间隔时间
     * @param minDate 小的时间
     * @param maxDate 大的时间
     * @param unitEnum 时间单位
     * @return
     * @throws IllegalAccessException
     */
    public static long getIntervalDate(LocalDateTime minDate, LocalDateTime maxDate, DateUnitEnum unitEnum) throws IllegalAccessException {
        Duration between = Duration.between(minDate, maxDate);
        switch (unitEnum){
            case YEAR:
                return minDate.getYear() - maxDate.getYear();
            case MONTH:
                return minDate.getMonthValue() - maxDate.getMonthValue();
            case WEEK: //两日期星期差
                int i1 = minDate.get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR);
                int i2 = maxDate.get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR);
                return i1 - i2;
            case DAY:
                return between.toDays();
            case HOUR:
                return between.toHours();
            case MINUTE:
                return between.toMinutes();
            case SECOND:
                return between.toMillis() / DateConstant.MS_S_UNIT_CONVERSION;
            case MILLISECOND:
                return between.toMillis();
            case NANO:
                return between.toNanos();
        }
        throw new IllegalAccessException("未知的枚举类型");
    }

    /**
     * 设置一天的开始时间 00:00:00
     * @return  localDateTime为空时无效  否则正常
     */
    public DateUtils setDayStart() {
        if(localDateTime == null)
            return this;
        localDateTime = localDateTime.withHour(0)
                .withMinute(0)
                .withSecond(0)
                .withNano(0);
        return this;
    }

    /**
     * 设置一天的结束时间 23:59:59
     * @return  localDateTime为空时无效  否则正常
     */
    public DateUtils setDayEnd() {
        if(localDateTime == null)
            return this;
        localDateTime = localDateTime.withHour(23)
                .withMinute(59)
                .withSecond(59)
                .withNano(99)
        ;
        return this;
    }

    /**
     * 获取转换的代数天 例如：输入30 返回1个月 输入 365返回1年
     * @param day
     * @return
     */
    public static DoubleResult<Integer, DateUnitEnum> getTransitionDayX(Integer day){
        if(day % oneMonthX == 0){
            return new DoubleResult<>(day / oneMonthX, DateUnitEnum.MONTH);
        }
        else if(day  % oneYearX == 0){
            return new DoubleResult<>(day / oneYearX, DateUnitEnum.YEAR);
        }
        else{
            return new DoubleResult<>(day, DateUnitEnum.DAY);
        }
    }

    public DateUtils getLaterRealDay(Integer day) throws ReflectException {
        getLaterRealDay(day, false);
        return this;
    }

    /**
     * 获取真实天数后的日期
     * @param day 天数 以30代指 1个月 以365代指 1年 可以是倍数 如 90即3个月
     * @param end 是否设置为结束时间 23:59:59 否则为 00:00:00
     *
     * @return
     */
    public DateUtils getLaterRealDay(Integer day, boolean end) throws ReflectException {
        DoubleResult<Integer, DateUnitEnum> dayX = getTransitionDayX(day);
        localDateTime = end
                ? addDate(dayX.getState(), dayX.getData()).setDayEnd().getLocalDateTime()
                :addDate(dayX.getState(), dayX.getData()).setDayStart().getLocalDateTime();
        return this;
    }

}
